#include <numeric>
#include <nice2d/widgets/gridlayout.h>

#define DEFAULT_GRIDLAYOUT_COL_WIDTH  60.0f
#define DEFAULT_GRIDLAYOUT_ROW_HEIGHT 48.0f

IMPLEMENT_OBJECT(GridLayout, Layout)

class GridLayoutInfo : public LayoutInfo
{
public:
    GridLayoutInfo() = delete;
    GridLayoutInfo(uint32_t newRow, uint32_t newCol, uint32_t newRowspan, uint32_t newColspan):
        row(newRow),
        col(newCol),
        rowspan(newRowspan),
        colspan(newColspan)
    {
    }

    uint32_t row;
    uint32_t col;
    uint32_t rowspan;
    uint32_t colspan;
};

class GridLayoutData
{
public:
    GridLayout *layout = nullptr;

    std::vector<float> colWidths;
    std::vector<float> rowHeights;

    void adjustControls();
    void adjustControl(std::shared_ptr<GridLayoutInfo> info, std::shared_ptr<Control> control);
};

GridLayout::GridLayout(uint32_t rowcount, uint32_t colcount):
    data(new GridLayoutData())
{
    data->layout = this;
    data->colWidths.resize(colcount);
    std::fill(data->colWidths.begin(), data->colWidths.end(), DEFAULT_GRIDLAYOUT_COL_WIDTH);
    data->rowHeights.resize(rowcount);
    std::fill(data->rowHeights.begin(), data->rowHeights.end(), DEFAULT_GRIDLAYOUT_ROW_HEIGHT);
}

GridLayout::~GridLayout()
{
}

void GridLayout::addControl(ControlPointer control, uint32_t row, uint32_t col, uint32_t rowspan,
                            uint32_t colspan)
{
    if (!control)
        return;

    if (data->colWidths.size() <= col)
        return;

    if (data->rowHeights.size() <= row)
        return;

    if (row + rowspan > data->rowHeights.size())
        rowspan = data->rowHeights.size() - row;

    if (col + colspan > data->colWidths.size())
        colspan = data->colWidths.size() - col;

    auto info = std::make_shared<GridLayoutInfo>(row, col, rowspan, colspan);
    addWidget(control, info);
}

void GridLayout::setColWidth(uint32_t col, float width)
{
    if (col < data->colWidths.size())
        data->colWidths[col] = width;
}

void GridLayout::setRowHeight(uint32_t row, float height)
{
    if (row < data->rowHeights.size())
        data->rowHeights[row] = height;
}

void GridLayout::adjust()
{
    float width = std::accumulate(data->colWidths.begin(), data->colWidths.end(), 0.0f);
    width += 2.0f * getMargin();
    if (data->colWidths.size() > 1)
        width += (data->colWidths.size() - 1) * getSpacing();

    float height = std::accumulate(data->rowHeights.begin(), data->rowHeights.end(), 0.0f);
    height += 2.0f * getMargin();
    if (data->rowHeights.size() > 1)
        height += (data->rowHeights.size() - 1) * getSpacing();

    setSize(width, height);
    data->adjustControls();
}

void GridLayoutData::adjustControls()
{
    auto count = layout->getWidgetCount();
    if (count == 0)
        return;

    for (uint32_t i = 0; i < count; i++) {
        auto item = layout->getWidgetByIndex(i);
        auto widget = item.first;

        auto info = std::dynamic_pointer_cast<GridLayoutInfo>(item.second);
        adjustControl(info, widget);
    }
}

void GridLayoutData::adjustControl(std::shared_ptr<GridLayoutInfo> info,
                                   std::shared_ptr<Control> control)
{
    sf::Vector2f position(layout->getMargin(), layout->getMargin());
    if (info->col >= 1) {
        position.x += info->col * layout->getSpacing();
        for (int i = 0; i < info->col; i++)
            position.x += colWidths[i];
    }

    if (info->row >= 1) {
        position.y += info->row * layout->getSpacing();
        for (int i = 0; i < info->row; i++)
            position.y += rowHeights[i];
    }

    control->setPosition(position);

    sf::Vector2f size((info->colspan - 1)*layout->getSpacing(),
                      (info->rowspan - 1)*layout->getSpacing());
    for (uint32_t i = 0; i < info->colspan; i++)
        size.x += colWidths[i + info->col];

    for (uint32_t i = 0; i < info->rowspan; i++)
        size.y += rowHeights[i + info->row];

    control->setSize(size);
}

